import React from 'react'

// 子组件里只依赖了父组件里传入的a参数
// 但是只要是父组件任意一个状态发生变化都会造成props的引用发生变化
// react就会认为props本身发生了变化，于是重新渲染当前组件
// 所以，我们可以使用react.memo来对props做一个过滤，避免父组件其它状态发生变化导致子组件重新渲染

// react.memo接受两个参数，第一个参数是我们的组件本身
// 第二个参数是一个回调函数，react会将上一次的props与当前的props传入这个函数，然后我们就可以通过对状态的比较来通知react是否进行渲染
const Test = React.memo(
  function (props: any) {
    console.log('props属性变化会造成子组件的反复渲染')
    return (
      <div style={{ border: '1px solid #333' }}>
        <div>父组件通过props传入的attr属性：{props.a}</div>
        <button onClick={props.changeA}>child click</button>
        {/* // 用一个标签把父组件的center包起来，这种被称之为嵌套 */}
        <div>{props.children}</div>
      </div>
    )
  }
  // (prevProps: any, nextProps: any) => {
  //   console.log(prevProps, nextProps)

  //   // 判断它们是否相同
  //   // 如果相同，不更新
  //   return prevProps.a === nextProps.a
  // }
)

// setState在类组件的生命周期中调用是异步，在setTimeout或事件监听中是同步！！

export default function Hook() {
  // 它是一个方法，我们调用它后，它会返回一个数组，数组的第一个元素是我们写入的值，第二个元素是用于修改这个值的方法
  // 它只在函数组件被构建时执行，当函数中有状态发生变化重新执行时，react会直接去寻找内存中对应的返回结果而不是再次执行
  // 所以，它是一个闭包函数，它的返回结果不受react状态更新的影响，它的作用相当于是类组件中的constructor
  const [a, setA] = React.useState('aaa')
  const [c, setC] = React.useState(1)
  const [, setB] = React.useState(Date.now())

  // 它也是一个方法，它接受两个参数，第一个参数是我们需要在组件执行时进行计算或其它操作的回调函数，第二个参数是它的依赖
  // 如果我们没有传入第二个参数，那么它其实是不生效的，写在它内部的回调函数会在每一次状态更新时重新执行
  React.useEffect(() => {
    // console.log('这个useEffect没有第二个参数，所以每次状态发生变化它都会执行')
  })
  // 如果我们传入了第二个参数，但它是一个空数组，表示当前这个函数没有依赖，不需要更新
  // 所以它只在当前组件构建时执行，后续与useState一样，直接从内存中取对应的计算结果
  // 它的作用相当于是componentDidMount
  React.useEffect(() => {
    // 在这里表示在componentDidMount时给ref赋值
    if (b.current) {
      b.current = { a: c }
      setB(Date.now())
    }
    // 每一次c发生变化时，重新生成计时器
    // const timer = setInterval(() => {
    //   console.log('计时器执行了', c)

    //   setC(c + 1)
    // }, 500)
    // react是一个单向数据流的库，所以，没有经过useState或setState去更新的数据，react是感知不到它的变化的，
    // 所以它能渲染的值始终是这个值变化之前的内容
    // 这里，b.current的值被渲染到页面时，始终是a或c发生变化后重新渲染之前获取到的值
    // console.log(c, b.current.a, '##########################')
    // console.log('有第二个参数，但没有元素，它只在组件构建时执行')
    // 它没有依赖，所以只需要在组件卸载时执行就行
    // 每一次c发生变化时，清除计时器
    return () => {
      // clearInterval(timer)
      // console.log('这是卸载函数，组件卸载时执行')
    }
  }, [c])
  // 当我们在第二个参数中添加了它的依赖，就表示，它的计算结果依赖于指定的状态，当这个状态发生变化的时候，它的计算结果也会发生变化
  // 所以它会在每一次组件重新执行时检查依赖状态的内容是否改变，如果改变重新执行
  // 它的作用相当于是指定状态的componentDidUpdate
  React.useEffect(() => {
    // 有依赖，表示当前这个函数里的计算结果依赖于第二个参数中状态的值
    // console.log('有第二个参数，也有依赖的元素，它会在a状态发生变化时执行')
    // 每次依赖状态发生变化它都会被重新生成
    // abc = setInterval()
    // componentWillUnmount
    // 当我们在useEffect中return一个函数的时候，表示当前这个函数会在组件卸载时执行
    // 所以，在函数组件中，componentWillUnmount可以有很多个，每个useEffect里返回的函数都会被执行
    // 它表示当前依赖的状态已经被更新，所以对应的，卸载函数里的计算比如说计时器之类的，可能会被当前函数重新定义
    return () => {
      // console.log(
      //   '这个也是卸载函数，但是如果useEffect有依赖的话，那么它在这个依赖发生变化时也会被执行'
      // )
      // 每次状态发生变化，都必须清除掉它，否则它会变成很多很多
      // clearInterval(abc)
    }
  }, [a])

  // 它的功能与类组件中的createRef类似，也是创建一个对象，然后在某个时间去给这个对象赋值
  // 要注意的是，我们不能给它本身赋值，只能操作它内部的current属性
  // useRef有个好处就是，一旦给它赋值，除非我们手动改变它，否则到组件卸载时它都不会发生变化
  // 所以，很多时候我们都用它来管理一些不会影响到render的属性，跟在类组件中定义this.xxx 一致
  const b = React.useRef({ a: 0 })
  // b 永远不会变化，变化的只是它内部的current属性

  // 因为函数组件每一次状态发生变化时，都会重新执行组件函数，
  // 所以，组件里定义的每一个函数都会在组件渲染时重新生成，所以，它们的引用地址就发生了变化
  // 子组件就会认为props有了变化，进而重新渲染

  // 使用更好的方式来避免函数被重新创建
  // 使用useCallback来缓存当前函数，避免被重新创建
  // 与useEffect是一样的，它也需要依赖，当依赖是一个空数组的时候，表示当前这个函数不依赖任何状态，它的计算结果只跟组件初始值有关
  // 如果有依赖，表示它的返回结果跟指定的依赖相关，每次状态发生变化必须重新生成新的函数
  // 在react函数组件里，所有hook都是闭包，所以如果我们不去更新这个函数，那就表示当前这个函数里访问的所有状态都是它被定义时读取到的状态
  // useCallback返回的是第一个参数本身
  const changeA = React.useCallback(() => {
    setA(a + 1)
  }, [a])

  // useMemo与useCallback唯一不同的一点就是，它返回是它第一个参数的执行结果而不是参数本身
  // react会先执行第一个参数后将计算的结果返回回来
  const d = React.useMemo(() => {
    return c + 999
  }, [c])

  // 3个常用hook
  // useState, useEffect, useRef

  // 用于优化的hook和memo
  // memo不是hook,它只是一个高阶函数，它接受一个组件函数并在其内部通过代理模式进行计算后返回新的函数组件
  // React.memo，如果传了第二个参数，与shouldComponentUpdate用法一致,要注意的是，它们的结果是相反的。shouldComponentUpdate返回true是渲染，memo返回true是不渲染
  // ，如果不传第二个参数，相当于是pureComponent
  // useCallback 防止父组件状态变化造成组件中定义的函数重新生成从而造成函数引用地址发生变化而被子组件误认为props中属性变化而重新渲染
  // useMemo 用于缓存计算的结果，避免组件重新渲染时反复计算

  // 两个不常用的hook
  // useContext 这个,只是为了方便我们在函数组件中使用react.context，用了useContext后我们可以在子组件中直接调用context中的value值
  // useReducer 看名字就能知道，它跟redux的reduce几乎是一致的，唯一的区别是它操作值存储的位置是当前组件中定义的对象或context中定义的对象
  return (
    <div>
      a:{a}; b: {b.current.a && b.current.a} c:{c};
      <button onClick={() => setA(a + 1)}>click me</button>
      <button onClick={() => setC(c + 1)}>c++</button>
      <Test a={a} changeA={changeA}>
        {d}
      </Test>
    </div>
  )
}
